unit DIrr.Event;

{$I DIrr.inc}

interface

uses
  DIrr.Types, DIrr.IO, DIrr.Base, DIrr.Vector2D, DIrr.Rect, DIrr.Dimension2D,
  DIrr.Collections, DIrr.Mem;

type
  TGUIElement = class;

  TCtrlKey = (ckShift, ckCtrl);
  TCtrlKeys = set of TCtrlKey;

  TComboKeyState = (cksPressed, cksShift, cksCtrl);
  TComboKeyStates = set of TComboKeyState;

  SEvent = record
  public type
    SGUIEvent = record
      //! IGUIElement who called the event
      Caller: TGUIElement;
      //! If the event has something to do with another element, it will be held here.
      Element: TGUIElement;
      //! Type of GUI Event
      EventType: EGUI_EVENT_TYPE;
    end;
    SMouseInput = record
      //! X position of mouse cursor
      X: S32;
      //! Y position of mouse cursor
      Y: S32;
      //! mouse wheel delta, often 1.0 or -1.0, but can have other values < 0.f or > 0.f;
      (** Only valid if event was EMIE_MOUSE_WHEEL *)
      Wheel: F32;
      CtrlKeys: TCtrlKeys;
      //! A bitmap of button states. You can use isButtonPressed() to determine
      //! if a button is pressed or not.
      //! Currently only valid if the event was EMIE_MOUSE_MOVED
      ButtonStates: TMouseButtonStates;

      //! Type of mouse event
      Event: EMOUSE_INPUT_EVENT;

      function IsLeftPressed: Boolean; {$IFDEF INLINE} inline; {$ENDIF}
      function IsRightPressed: Boolean; {$IFDEF INLINE} inline; {$ENDIF}
      function IsMiddlePressed: Boolean; {$IFDEF INLINE} inline; {$ENDIF}
    end;

    SKeyInput = record
      //! Character corresponding to the key (0, if not a character)
      CharValue: WideChar;
      //! Key which has been pressed or released
      Key: EKEY_CODE;

      ComboKeys: TComboKeyStates; //! If not cksPressed, then the key was left up
    private
      function GetPressedDown: Boolean; inline;
      procedure SetPressedDown(const Value: Boolean); inline;
      function GetControl: Boolean; inline;
      function GetShift: Boolean; inline;
      procedure SetControl(const Value: Boolean); inline;
      procedure SetShift(const Value: Boolean); inline;
    public
      property PressedDown: Boolean read GetPressedDown write SetPressedDown;
      property Shift: Boolean read GetShift write SetShift;
      property Control: Boolean read GetControl write SetControl;
    end;

    PJoystickEvent = ^SJoystickEvent;
    SJoystickEvent = record
      ButtonStates: U32;
      (** For AXIS_X, AXIS_Y, AXIS_Z, AXIS_R, AXIS_U and AXIS_V
       * Values are in the range -32768 to 32767, with 0 representing
       * the center position.  You will receive the raw value from the
       * joystick, and so will usually want to implement a dead zone around
       * the center of the range. Axes not supported by this joystick will
       * always have a value of 0. On Linux, POV hats are represented as axes,
       * usually the last two active axis.       *)
      Axis: array[TJoystickAxis] of S16;
      (** The POV represents the angle of the POV hat in degrees * 100,
       * from 0 to 35,900.  A value of 65535 indicates that the POV hat
       * is centered (or not present).
       * This value is only supported on Windows.  On Linux, the POV hat
       * will be sent as 2 axes instead. *)
      POV: U16;
      //! The ID of the joystick which generated this event.
      (** This is an internal Irrlicht index; it does not map directly
       * to any particular hardware joystick. *)
      Joystick: U8;
      //! A helper function to check if a button is pressed.
      function IsButtonPressed(Button: U32): Boolean; {$IFDEF INLINE} inline; {$ENDIF}
    end;
    SLogEvent = record
      Text: PAnsiChar;
      Level: ELOG_LEVEL;
    end;
    SUserEvent = record
      //! Some user specified data as int
      UserData1: S32;
      //! Another user specified data as int
      UserData2: S32;
    end;
  public
    EventType: EEVENT_TYPE;
    case Integer of
      0: (GUIEvent: SGUIEvent);
      1: (MouseInput: SMouseInput);
      2: (KeyInput: SKeyInput);
      3: (JoystickEvent: SJoystickEvent);
      4: (LogEvent: SLogEvent);
      5: (UserEvent: SUserEvent);
  end;

  //! Information on a joystick, returned from @ref irr::IrrlichtDevice::activateJoysticks()
  // For read only
  SJoystickInfo = record
  private type
    TPOVType =
      (
        //! A hat is definitely present.
        POV_HAT_PRESENT,

        //! A hat is definitely not present.
        POV_HAT_ABSENT,

        //! The presence or absence of a hat cannot be determined.
        POV_HAT_UNKNOWN
      );
  public
    //! The ID of the joystick
    (** This is an internal Irrlicht index; it does not map directly
     * to any particular hardware joystick. It corresponds to the
     * irr::SJoystickEvent Joystick ID. *)
    Joystick: U8;
    //! The name that the joystick uses to identify itself.
    Name: TIrrAnsiString;
    //! The number of buttons that the joystick has.
    Buttons: U32;
    //! The number of axes that the joystick has, i.e. X, Y, Z, R, U, V.
    (** Note: with a Linux device, the POV hat (if any) will use two axes. These
     *  will be included in this count. *)
    Axes: U32;
    //! An indication of whether the joystick has a POV hat.
    (** A Windows device will identify the presence or absence or the POV hat.  A
     *  Linux device cannot, and will always return POV_HAT_UNKNOWN. *)
    PovHat: TPOVType;
  end;

  TJoystickInfoArray = TArrayWrapObj<SJoystickInfo>;

  TEventReceiver = class abstract(TAttributeExchangingObject)
  public
    function OnEvent(const Event: SEvent): Boolean; virtual; stdcall;
  end;

  TGUIElement = class abstract(TEventReceiver)
  public type
    TGUIElementList = SObjectList<TGUIElement>;

  strict protected
    //! List of all children of this element
    FChildren: TGUIElementList;

    //! Pointer to the parent
    FParent: TGUIElement;

    //! relative rect of element
    FRelativeRect: TIrrRect;

    //! absolute rect of element
    FAbsoluteRect: TIrrRect;

    //! absolute clipping rect of element
    FAbsoluteClippingRect: TIrrRect;

    //! the rectangle the element would prefer to be,
    //! if it was not constrained by parent or max/min size
    FDesiredRect: TIrrRect;

  	//! for calculating the difference when resizing parent
	  FLastParentRect: TIrrRect;

  	//! relative scale of the element inside its parent
	  FScaleRect: TIrrRectF;

    //! maximum and minimum size of the element
    FMaxSize: TDimension2DU;
    FMinSize: TDimension2DU;

    //! is visible?
    FIsVisible: Boolean;

    //! is enabled?
    FIsEnabled: Boolean;

    //! is a part of a larger whole and should not be serialized?
    FIsSubElement: Boolean;

    //! does this element ignore its parent's clipping rectangle?
    FNoClip: Boolean;

    //! caption
    FText: TIrrWideString;

    //! tooltip
    FToolTipText: TIrrWideString;

    //! users can set this for identificating the element by string
    FName: TIrrAnsiString;

    //! users can set this for identificating the element by integer
    FID: S32;

    //! tab stop like in windows
    FIsTabStop: Boolean;

    //! tab order
    FTabOrder: S32;

    //! tab groups are containers like windows, use ctrl+tab to navigate
    FIsTabGroup: Boolean;

    //! tells the element how to act when its parent is resized
    FAlignLeft, FAlignRight, FAlignTop, FAlignBottom: EGUI_ALIGNMENT;

    //! GUI Environment
    FEnvironment: TNamedObject{TGUIEnvironment};

    //! type of element
    FType: EGUI_ELEMENT_TYPE;
  public
    constructor Create;
    destructor Destroy; override;
  public
    procedure SetAlignment(Left, Right, Top, Bottom: EGUI_ALIGNMENT);
  public
    procedure UpdateAbsolutePosition; virtual; stdcall; abstract;
    function IsPointInside(const Point: TVector2D): Boolean; virtual; stdcall; abstract;
    procedure AddChild(Child: TGUIElement); virtual; stdcall; abstract;
    procedure RemoveChild(Child: TGUIElement); virtual; stdcall; abstract;
    procedure Remove; virtual; stdcall; abstract;
    procedure Draw; virtual; stdcall; abstract;
    procedure OnPostRender(timeMs: U32); virtual; stdcall; abstract;
    procedure Move(AbsoluteMovement: TVector2D); virtual; stdcall; abstract;
    function IsVisible: Boolean; virtual; stdcall; abstract;
    procedure SetVisible(Visible: Boolean); virtual; stdcall; abstract;
    function IsSubElement: Boolean; virtual; stdcall; abstract;
    procedure SetSubElement(SubElement: Boolean); virtual; stdcall; abstract;
    function IsEnabled: Boolean; virtual; stdcall; abstract;
    procedure SetEnabled(Enabled: Boolean); virtual; stdcall; abstract;
    procedure SetText(const Text: PWideChar); virtual; stdcall; abstract;
    function GetText: PWideChar; virtual; stdcall; abstract;
    procedure SetToolTipText(const Text: PWideChar); virtual; stdcall; abstract;
    function GetToolTipText: PWideChar; virtual; stdcall; abstract;
    function GetID: S32; virtual; stdcall; abstract;
    procedure SetID(id: S32); virtual; stdcall; abstract;
    function BringToFront(Element: TGUIElement): Boolean; virtual; stdcall; abstract;
    function SendToBack(Child: TGUIElement): Boolean; virtual; stdcall; abstract;
    function GetChildren: TGUIElementList; virtual; stdcall; abstract;
    function GetElementFromID(id: S32; SearchChildren: Boolean = False
      ): TGUIElement; virtual; stdcall; abstract;
    function HasType(typeval: EGUI_ELEMENT_TYPE): Boolean; virtual; stdcall; abstract;
    function GetTypeName: PAnsiChar; virtual; stdcall; abstract;
    function GetName: PAnsiChar; virtual; stdcall; abstract;
  strict private
    {$HINTS OFF}
    procedure _Hold_SetName(v: Pointer); virtual; stdcall; abstract;
    {$HINTS ON}
  public
    procedure SetName(const Name: PAnsiChar); virtual; stdcall; abstract;
  public
    property Parent: TGUIElement read FParent;
  end;


implementation

{ TEventReceiver }

function TEventReceiver.OnEvent(const Event: SEvent): Boolean;
begin
  Result := False;
end;

{ SEvent.SMouseInput }

function SEvent.SMouseInput.IsLeftPressed: Boolean;
begin
  Result := mbsLeft in ButtonStates;
end;

function SEvent.SMouseInput.IsMiddlePressed: Boolean;
begin
  Result := mbsMiddle in ButtonStates;
end;

function SEvent.SMouseInput.IsRightPressed: Boolean;
begin
  Result := mbsRight in ButtonStates;
end;

{ SEvent.SJoystickEvent }

function SEvent.SJoystickEvent.IsButtonPressed(Button: U32): Boolean;
const NUMBER_OF_BUTTONS = 32;
begin
  if (Button > NUMBER_OF_BUTTONS) then
  begin
    Result := False;
    Exit;
  end;

  Result := ((ButtonStates and (1 shl Button)) <> 0);
end;

{ TGUIElement }

constructor TGUIElement.Create;
begin
  FChildren.Init;
  FToolTipText.Init('');
  FText.Init('');
  FName.Init('');
end;

destructor TGUIElement.Destroy;
begin
  FName.Uninit;
  FText.Uninit;
  FToolTipText.Uninit;
  FChildren.Uninit;
  inherited;
end;

procedure TGUIElement.SetAlignment(Left, Right, Top, Bottom: EGUI_ALIGNMENT);
var
  d: TDimension2DF;
begin
  FAlignLeft := Left;
  FAlignRight := Right;
  FAlignTop := Top;
  FAlignBottom := Bottom;

  if Assigned(FParent) then
  begin
    d.Init(FParent.FAbsoluteRect.GetSize.Width, FParent.FAbsoluteRect.GetSize.Height);

    if (FAlignLeft = EGUIA_SCALE) then
      FScaleRect.UpperLeftCorner.X := FDesiredRect.UpperLeftCorner.X / d.Width;
    if (FAlignRight = EGUIA_SCALE) then
      FScaleRect.LowerRightCorner.X := FDesiredRect.LowerRightCorner.X / d.Width;
    if (FAlignTop = EGUIA_SCALE) then
      FScaleRect.UpperLeftCorner.Y := FDesiredRect.UpperLeftCorner.Y / d.Height;
    if (FAlignBottom = EGUIA_SCALE) then
      FScaleRect.LowerRightCorner.Y := FDesiredRect.LowerRightCorner.Y / d.Height;
  end;
end;

{ SEvent.SKeyInput }

function SEvent.SKeyInput.GetControl: Boolean;
begin
  Result := cksCtrl in ComboKeys;
end;

function SEvent.SKeyInput.GetPressedDown: Boolean;
begin
  Result := cksPressed in ComboKeys;
end;

function SEvent.SKeyInput.GetShift: Boolean;
begin
  Result := cksShift in ComboKeys;
end;

procedure SEvent.SKeyInput.SetControl(const Value: Boolean);
begin
  if Value then
    Include(ComboKeys, cksCtrl)
  else
    Exclude(ComboKeys, cksCtrl);
end;

procedure SEvent.SKeyInput.SetPressedDown(const Value: Boolean);
begin
  if Value then
    Include(ComboKeys, cksPressed)
  else
    Exclude(ComboKeys, cksPressed);

end;

procedure SEvent.SKeyInput.SetShift(const Value: Boolean);
begin
  if Value then
    Include(ComboKeys, cksShift)
  else
    Exclude(ComboKeys, cksShift);

end;

end.
